﻿using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;

using Daybreak.Core.SceneGraph;
using Daybreak.Core.Rendering.Pipeline;

namespace Daybreak.Core.Rendering
{
    /// <summary>
    /// Walks through the scene and collects all visible nodes (builds VisibleQueue)
    /// </summary>
    public class CullVisitor : NodeVisitor, IVisibleQueue
    {
        RenderArgs curItem;
        TimeSpanWatcher _timeStep;


        private List<VisibleItem> _visibleItems;
        private int _itemsCount;
        private Camera _camera;
        private Pipes.FrameContext _frameData;

        public CullVisitor(Pipes.FrameContext frameData, float updatePerSecond)
        {
            _timeStep = new TimeSpanWatcher(updatePerSecond);
            _visibleItems = new List<VisibleItem>(128);
            _frameData = frameData;
        }

        public void VisitScene(Scene.VisualScene scene, Camera camera, float elapsedTime)
        {
            float delta = _timeStep.GetNextDelta(elapsedTime);

            // cmera should be updated already
            _camera = camera;

            if (delta > 0)
            {
                curItem.TransformMatrix = Matrix.Identity;
                curItem.TransformNode = null;

                _itemsCount = 0;

                scene.ApplyVisitor(this);

            }
        }

        public override void VisitGroup(Group group)
        {
            for (int i = 0; i < group.ChildrenCount; i++)
            {
                // save current transform before going deeper through hierarchy
                Matrix saveMatrix = curItem.TransformMatrix;
                Transform saveNode = curItem.TransformNode;

                // go deeper through hierarchy ...
                group[i].Accept(this);

                curItem.TransformMatrix = saveMatrix;
                curItem.TransformNode = saveNode;
            }

        }

        public override void VisitTransform(Transform transform)
        {
            // save world transform
            Matrix saveMatrix = curItem.TransformMatrix;
            Transform saveNode = curItem.TransformNode;

            // set new world transform
            transform.LocalToWorld(ref curItem.TransformMatrix);
            curItem.TransformNode = transform;

            // go deeper
            VisitGroup(transform);

            // restore transform
            curItem.TransformMatrix = saveMatrix;
            curItem.TransformNode = saveNode;
        }

        public override void VisitGeode(Geode geode)
        {

            for (int i = 0; i < geode.Parts.Count; i++)
            {
                Drawable.Container geomContainer = geode.Parts[i];

                if (geomContainer == null)
                    continue;

                curItem.BoundingBox = new BoundingBox();
                geomContainer.MergeBounding(ref curItem.BoundingBox);

                // transform bounding volume to world space
                Transform.TransformBox(ref curItem.BoundingBox, ref curItem.TransformMatrix, out curItem.BoundingBox);

                ContainmentType ctype = ContainmentType.Contains;

                _camera.Frustum.Contains(ref curItem.BoundingBox, out ctype);

                if (ctype == ContainmentType.Disjoint)
                    continue;

                VisibleItem item = AddItem(geomContainer);

                item.RenderArgs = curItem;

            }
        }

        public override void VisitTerrain(TerrainNode terrain)
        {
            VisibleItem item = AddItem(terrain);

            item.RenderArgs = curItem;

        }

        public override void VisitLight(Light light)
        {
            light.UpdateFrameData(_frameData, ref curItem);
        }

        private VisibleItem AddItem(IRenderable item)
        {
            VisibleItem vItem = null;

            if (_itemsCount >= _visibleItems.Count)
            {
                vItem = new VisibleItem(item);
                _visibleItems.Add(vItem);
                _itemsCount = _visibleItems.Count;

                return vItem;
            }

            vItem = _visibleItems[_itemsCount++];
            vItem.Renderable = item;

            return vItem;

        }

        public VisibleItem GetItem(int index)
        {
            return _visibleItems[index];
        }

        public int ItemsCount
        {
            get { return _itemsCount; }
        }

        public float UpdateRate
        {
            get { return _timeStep.UpdateRate; }
            set { _timeStep.UpdateRate = value; }
        }
    }

    public class VisibleItem
    {
        public RenderArgs RenderArgs;
        public IRenderable Renderable;

        public VisibleItem() { }
        
        public VisibleItem(IRenderable item)
        {
            Renderable = item;
        }

        public void RenderSelf(IRender render)
        {
            Renderable.RenderSelf(render, ref RenderArgs);
        }


    }

    public interface IVisibleQueue
    {
        VisibleItem GetItem(int index);
        int ItemsCount { get; }
    }

}
